﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace CoreRay
{
    public class Pixel
    {
        public Pixel()
        {
            this.Alpha = 1.0f;
        }

        public Spectrum Color
        {
            get;
            set;
        }

        public float Alpha
        {
            get;
            set;
        }

        public float WeightSum
        {
            get;
            set;
        }
    }

    public class Film
    {
        private Pixel[] pixelData;
        private float[] filterCache;
        private const int FilterCacheSize = 16;

        public Film(int width, int height, Filter filter)
        {
            this.Width = width;
            this.Height = height;
            this.Filter = filter;

            //Init pixel data array
            this.pixelData = new Pixel[width * height];

            for(int i = 0; i < width * height; i++)
            {
                this.pixelData[i] = new Pixel();
            }

            //Precompute filter results into the filter cache
            this.filterCache = new float[FilterCacheSize * FilterCacheSize];

            for(int i = 0; i < FilterCacheSize; i++)
            {
                float y = ((float) i + 0.5f) * this.Filter.Height / FilterCacheSize;

                for(int j = 0; j < FilterCacheSize; j++)
                {
                    float x = ((float) j + 0.5f) * this.Filter.Width / FilterCacheSize;

                    this.filterCache[i * FilterCacheSize + j] = this.Filter.Evaluate(x, y);
                }
            }

            this.PreviewImage = new WriteableBitmap(this.Width, this.Height, 96, 96, PixelFormats.Bgra32, null);
        }

        public int Width
        {
            get;
            private set;
        }

        public int Height
        {
            get;
            private set;
        }

        public Filter Filter
        {
            get;
            private set;
        }

        public BitmapSource PreviewImage
        {
            get;
            private set;
        }

        public void AddSample(Sample sample, Spectrum spectrum)
        {
            //Compute discreet sample coordinates
            float samplePositionX = sample.ImagePositionX - 0.5f;
            float samplePositionY = sample.ImagePositionY - 0.5f;

            int x0 = (int) Math.Ceiling(samplePositionX - this.Filter.Width);
            int x1 = (int) Math.Floor(samplePositionX + this.Filter.Width);
            int y0 = (int) Math.Ceiling(samplePositionY - this.Filter.Height);
            int y1 = (int) Math.Floor(samplePositionY + this.Filter.Height);

            //TODO: Change this when there will be the concept of crop window
            x0 = Math.Max(x0, 0);
            x1 = Math.Min(x1, 0 + this.Width - 1);
            y0 = Math.Max(y0, 0);
            y1 = Math.Min(y1, 0 + this.Height - 1);

            //Compute cache indexes
            int[] cacheIndexesX = new int[x1 - x0 + 1];

            for(int x = x0; x <= x1; x++)
            {
                float fx = Math.Abs((x - samplePositionX) * this.Filter.Width * FilterCacheSize);
                cacheIndexesX[x - x0] = (int) Math.Min(Math.Floor(fx), FilterCacheSize - 1);
            }

            int[] cacheIndexesY = new int[y1 - y0 + 1];

            for(int y = y0; y <= y1; y++)
            {
                float fy = Math.Abs((y - samplePositionY) * this.Filter.Height * FilterCacheSize);
                cacheIndexesY[y - y0] = (int) Math.Min(Math.Floor(fy), FilterCacheSize - 1);
            }


            for(int y = y0; y <= y1; y++)
            {
                for(int x = x0; x <= x1; x++)
                {
                    int pixelIndex = y * this.Width + x;
                    int filterCacheIndex = cacheIndexesY[y - y0] * FilterCacheSize + cacheIndexesX[x - x0];

                    float weight = this.filterCache[filterCacheIndex];

                    this.pixelData[pixelIndex].Color += spectrum * weight;
                    this.pixelData[pixelIndex].Alpha = 1.0f;
                    this.pixelData[pixelIndex].WeightSum += weight;
                }
            }
        }

        public void UpdatePreviewImage(RenderRegion renderRegion)
        {
            byte[] data = new byte[renderRegion.Width * renderRegion.Height * 4];

            for(int i = renderRegion.PositionY; i < renderRegion.PositionY + renderRegion.Height; i++)
            {
                for(int j = renderRegion.PositionX; j < renderRegion.PositionX + renderRegion.Width; j++)
                {
                    Pixel pixel = this.pixelData[i * this.Width + j];

                    Spectrum spectrumColor = pixel.Color;

                    if(pixel.WeightSum != 0.0f)
                    {
                        spectrumColor = spectrumColor / pixel.WeightSum;
                    }

                    Color color = spectrumColor.ToColor();
                    color.Alpha = pixel.Alpha;

                    byte alpha = (byte) (color.Alpha * 255);
                    byte red = (byte) (Math.Min(color.Red, 1.0f) * 255);
                    byte green = (byte) (Math.Min(color.Green, 1.0f) * 255);
                    byte blue = (byte) (Math.Min(color.Blue, 1.0f) * 255);

                    data[(i - renderRegion.PositionY) * renderRegion.Width * 4 + (j - renderRegion.PositionX) * 4] = (byte) blue;
                    data[(i - renderRegion.PositionY) * renderRegion.Width * 4 + (j - renderRegion.PositionX) * 4 + 1] = (byte) green;
                    data[(i - renderRegion.PositionY) * renderRegion.Width * 4 + (j - renderRegion.PositionX) * 4 + 2] = (byte) red;
                    data[(i - renderRegion.PositionY) * renderRegion.Width * 4 + (j - renderRegion.PositionX) * 4 + 3] = (byte) alpha;
                }
            }

            this.PreviewImage.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, (DispatcherOperationCallback) delegate(object arg)
            {
                ((WriteableBitmap) this.PreviewImage).WritePixels(new Int32Rect(renderRegion.PositionX, renderRegion.PositionY, renderRegion.Width, renderRegion.Height), data, renderRegion.Width * 4, 0);
                return null;

            }, null);
        }

        public void WriteImage(string fileName)
        {
            byte[] data = new byte[this.Width * this.Height * 3];

            for(int i = 0; i < this.Height; i++)
            {
                for(int j = 0; j < this.Width; j++)
                {
                    Pixel pixel = this.pixelData[i * this.Width + j];

                    if(pixel.WeightSum != 0.0f)
                    {
                        pixel.Color = pixel.Color / pixel.WeightSum;
                    }

                    Color color = pixel.Color.ToColor();
                    color.Alpha = pixel.Alpha;

                    byte alpha = (byte) (color.Alpha * 255);
                    byte red = (byte) (Math.Min(color.Red, 1.0f) * 255);
                    byte green = (byte) (Math.Min(color.Green, 1.0f) * 255);
                    byte blue = (byte) (Math.Min(color.Blue, 1.0f) * 255);

                    data[i * this.Width * 3 + j * 3] = blue;
                    data[i * this.Width * 3 + j * 3 + 1] = green;
                    data[i * this.Width * 3 + j * 3 + 2] = red;
                    // data[i * this.Width + j + 3] = alpha;

                    //bitmap.SetPixel(j, i, System.Drawing.Color.FromArgb(alpha, red, green, blue));
                }
            }

            BitmapSource bitmap = BitmapSource.Create(this.Width, this.Height, 96, 96, PixelFormats.Rgb24, null, data, this.Width * 3);

            BitmapEncoder bitmapEncoder = new BmpBitmapEncoder();
            bitmapEncoder.Frames.Add(BitmapFrame.Create(bitmap));

            bitmapEncoder.Save(new FileStream(fileName, FileMode.Create)); 
        }
    }
}
